/*
 * Copyright (C) Michael Martin & Antonio Carrozzini (TBJD)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package org.tbjd.client.guis.gantt;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.tbjd.client.borderDetectors.GanttBorderNotifier;
import org.tbjd.client.constants.GanttConstants;
import org.tbjd.client.constants.NotificationsConstants;
import org.tbjd.client.constants.ProjectConstants;
import org.tbjd.client.constants.TimeConstants;
import org.tbjd.client.constants.ZoomConstants;
import org.tbjd.client.exceptions.NoUpperDecisionmakerFoundException;
import org.tbjd.client.exceptions.NoUpperMediatorFoundException;
import org.tbjd.client.exceptions.TBJDException;
import org.tbjd.client.interfaces.BorderListener;
import org.tbjd.client.interfaces.Builder;
import org.tbjd.client.interfaces.Decisionmaker;
import org.tbjd.client.interfaces.Displayer;
import org.tbjd.client.interfaces.HasMediator;
import org.tbjd.client.interfaces.Observer;
import org.tbjd.client.mediators.AbstractMediator;

import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;

/**
 * <p>
 * The GanttTimeLineGui is the panel that displays a grid representing time (days, weeks...)
 * </p>
 * 
 * @author Michael Martin
 * @author Antonio Carrozzini
 * @version 1.0
 * 
 */
public class GanttTimeLineGui extends Composite implements Builder, Displayer, HasMediator, Decisionmaker, Observer, BorderListener {

	private Grid container = null;
	private AbstractMediator mediator = null;
	private static GanttTimeLineGui instance = null;
	private Date refDate = null;
	private Date[] headersDates = new Date[GanttConstants.TIMELINE_COLUMN_NUMBER];
	private List<Date> daysOff = null;

	private GanttTimeLineGui() {
		build();
		initWidget(this.container);
	}

	static public GanttTimeLineGui getInstance() {
		if (instance == null) {
			instance = new GanttTimeLineGui();
		}
		return instance;
	}

	public void build() {
		this.container = new Grid(2, GanttConstants.TIMELINE_COLUMN_NUMBER);
		this.container.setStyleName("gantt_timeLineGui");
		this.container.getRowFormatter().setStyleName(0, "gantt_timeLineGui_header");
		GanttBorderNotifier.getInstance().addBorderListener(this);

		// for tests only
		Date dayOff = new Date(new Date().getTime() + TimeConstants.DAY_IN_MILLIS * 3);
		System.out.println("GanttTimeLineGui.build() dayOff=" + dayOff);
		addDayOff(dayOff);
	}

	public Widget getBuildedWidget() {
		return GanttTimeLineGui.instance;
	}

	public void decide(Object action) {
	}

	public void delegate(Object action) throws NoUpperDecisionmakerFoundException, NoUpperMediatorFoundException {
		try {
			this.mediator.decide(action);

		} catch (TBJDException e) {
			e.printStackTrace();
		}
	}

	public void handle(Object action) {
	}

	public void display() {
		System.out.println("GanttTimeLineGui.display()");

		// determine the headers values and set headers with them
		displayHeaders(computeHeadersValues(ProjectConstants.PROJECT_BEGINDATE));

		// set the container width (number of columns * columns sizes)
		this.container.setWidth(GanttConstants.TIMELINE_COLUMN_NUMBER * ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR + "px");

		// set the container height (100% of screen)
		this.container.setHeight("100%");
	}

	public void display(List<?> items) {
	}

	/**
	 * Determine the date for each displayed column header and return the result as a list of String
	 * 
	 * @param refDate
	 *            the date for the first column header
	 * @return a string array containing the dates to display
	 */
	private String[] computeHeadersValues(Date refDate) {
		String[] headers = new String[GanttConstants.TIMELINE_COLUMN_NUMBER];
		this.refDate = refDate;
		Date currentDate = this.refDate;

		for (int i = 0; i < GanttConstants.TIMELINE_COLUMN_NUMBER; i++) {
			headers[i] = DateTimeFormat.getFormat("dd.MM.yy").format(currentDate);
			this.headersDates[i] = currentDate;
			currentDate = new Date(currentDate.getTime() + TimeConstants.DAY_IN_MILLIS);
		}

		return headers;
	}

	/**
	 * <p>
	 * Check the status of each column to set the corresponding column the appropriate style
	 * </p>
	 * <p>
	 * If a column date is in the list of non-working days or weekend, its background gets a different style
	 * </p>
	 */
	private void checkDayStatus() {
		// if there are days off set
		if (this.daysOff != null && this.daysOff.size() > 0) {

			// and for each day off
			for (Date eachDayOff : this.daysOff) {

				// for each column of the grid
				for (int i = 0; i < GanttConstants.TIMELINE_COLUMN_NUMBER - 1; i++) {

					// if its a day off or a weekend
					if (DateTimeFormat.getFormat("dd MMM yy").format(eachDayOff).equalsIgnoreCase(
							DateTimeFormat.getFormat("dd MMM yy").format(this.headersDates[i]))
							|| (isWeekend(this.headersDates[i]))) {
						this.container.getColumnFormatter().setStyleName(i, "gantt_timeLineGui_day_off");
					} else {
						this.container.getColumnFormatter().setStyleName(i, "gantt_timeLineGui_day_on");
					}
				}
			}
		}
	}

	/**
	 * Format and display columns header with their date as a String
	 * 
	 * @param computeHeadersValues
	 *            the list of headers's date
	 */
	private void displayHeaders(String[] computeHeadersValues) {
		Label header = null;
		for (int i = 0; i < computeHeadersValues.length; i++) {
			header = new Label(computeHeadersValues[i]);
			this.container.setWidget(0, i, header);
			this.container.getCellFormatter().setHeight(0, i, GanttConstants.TIMELINE_HEADER_HEIGHT + "px");
			this.container.getCellFormatter().setWidth(0, i, ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR + "px");
		}

		checkDayStatus();
	}

	/**
	 * <p>
	 * For the given day, return true if its a saturday or a sunday
	 * </p>
	 * 
	 * @param day
	 *            the day to test
	 * @return boolean true if it is in a weekend
	 */
	private boolean isWeekend(Date day) {
		String dayToString = DateTimeFormat.getFullDateFormat().format(day).split(",")[0];
		// FIXME how to test the day of week independently of the locale ?
		return (dayToString.equalsIgnoreCase("Saturday") || dayToString.equalsIgnoreCase("Sunday"));
	}

	/**
	 * Adds a day off in the list of days off
	 * 
	 * @param day
	 *            the day off to add
	 */
	public void addDayOff(Date day) {
		if (this.daysOff == null) {
			this.daysOff = new ArrayList<Date>();
		}
		this.daysOff.add(day);
	}

	/**
	 * Removes a day off in the list of days off
	 * 
	 * @param day
	 *            the day off to remove
	 */
	public void removeDayOff(Date day) {
		if (this.daysOff != null) {
			this.daysOff.remove(day);
		}
	}

	public void setMediator(AbstractMediator mediator) {
		this.mediator = mediator;
		this.mediator.addObserver(this);
	}

	public void executeBorderDetected(Widget draggable, int directionX, int directionY) {
		if (directionX > 0) {
			// go backward in time
			displayHeaders(computeHeadersValues(new Date(this.refDate.getTime() - TimeConstants.DAY_IN_MILLIS)));
		}

		if (directionX < 0) {
			// go forward in time
			displayHeaders(computeHeadersValues(new Date(this.refDate.getTime() + TimeConstants.DAY_IN_MILLIS)));
		}
	}

	public void update(Object sender) {
		System.out.println("GanttTimeLineGui.update()");
		if (((Map<?, ?>) sender).containsKey(NotificationsConstants.LIST_TASKS)) {
			display();
		}

	}

	public void createBorderListener(BorderListener listener) {

	}

}
